/*
 * Copyright (c) 2019 Arm Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * \file systimer_armv8-m_drv.h
 *
 * \brief Driver for Armv8-M System Timer
 *
 * This System Timer is based on the 64-bit Armv8-M System Counter,
 * generating the physical count for System Timer.
 *
 * Main features:
 *   - Disabling the timer doesn't stop counting, but it disables timer output
 *     signal, what might be a power saving option.
 *   - 1 interrupt signal, can be triggered by the 2 modes below
 *      Modes:
 *          1. Normal mode
 *          For clearing the interrupt generated by normal mode, the Timer
 *          should be disabled.
 *              Views
 *                  1.1. 64-bit up-counting Compare view
 *                       As soon as the physical up-counter reaches the set
 *                       compare value, the interrupt status will be asserted.
 *                       \ref systimer_armv8_m_set_compare_value
 *                       \ref systimer_armv8_m_get_compare_value

 *                  1.2. 32-bit down-counting Timer view
 *                       As soon as the down-counter timer reaches zero,
 *                       the interrupt status will be asserted.
 *                       Setting the down-counter timer value, sets the compare
 *                       register by
 *                         compare register = current counter + timer value
 *                       \ref systimer_armv8_m_set_timer_value
 *                       \ref systimer_armv8_m_get_timer_value
 *
 *          2. Auto-Increment mode
 *              - The auto-increment feature allows generation of Timer
 *                interrupt at regular intervals without the need for
 *                reprogramming the Timer after each interrupt and re-enabling
 *                the timer logic.
 *              - Auto-increment is working as a 64-bit up-counter, which is set
 *                by the 32-bit reload register.
 *              - If auto-increment mode is enabled, none of the normal modes'
 *                views can assert interrupt. *
 *              \ref systimer_armv8_m_get_autoinc_value
 *              \ref systimer_armv8_m_set_autoinc_reload
 *              \ref systimer_armv8_m_enable_autoinc
 *              \ref systimer_armv8_m_disable_autoinc
 *              \ref systimer_armv8_m_is_autoinc_enabled
 *              \ref systimer_armv8_m_clear_autoinc_interrupt
 *              \ref systimer_armv8_m_is_autoinc_implemented
 *
 */

#ifndef __SYSTIMER_ARMV8_M_DRV_H__
#define __SYSTIMER_ARMV8_M_DRV_H__

#include <stdbool.h>
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

#define SYSTIMER_ARMV8_M_REGISTER_BIT_WIDTH 32u
/*!< Armv8-M System Timer registers bit width */

/**
 *  \brief Armv8-M System Timer device configuration structure
 */
struct systimer_armv8_m_dev_cfg_t
{
    const uint32_t base;
    /*!< Armv8-M System Timer device base address */
    uint32_t default_freq_hz;
    /*!< Default reported frequency in Hz */
};

/**
 * \brief Armv8-M System Timer device data structure
 */
struct systimer_armv8_m_dev_data_t
{
    bool is_initialized;
};

/**
 * \brief Armv8-M System Timer device structure
 */
struct systimer_armv8_m_dev_t
{
    const struct systimer_armv8_m_dev_cfg_t * const cfg;
    /*!< Armv8-M System Timer configuration structure */
    struct systimer_armv8_m_dev_data_t * const data;
    /*!< Armv8-M System Timer data structure */
};

/**
 * \brief Initializes timer to a known default state, which is:
 *          - timer is enabled
 *          - interrupt is disabled
 *          - auto-increment is disabled
 *          - reported timer frequency is set to default
 *        Init should be called prior to any other process and
 *        it's the caller's responsibility to follow proper call order.
 *        More than one call results fall through.
 *
 * \param[in] dev Timer device struct \ref systimer_armv8_m_dev_t
 *
 * \note This function doesn't check if dev is NULL.
 */
void systimer_armv8_m_init(struct systimer_armv8_m_dev_t * dev);

/**
 * \brief Uninitializes timer to a known default state, which is:
 *          - timer is disabled
 *          - interrupt is disabled
 *          - auto-increment is disabled
 *        Init should be called prior to any other process and
 *        it's the caller's responsibility to follow proper call order.
 *        More than one call results fall through.
 *
 * \param[in] dev Timer device struct \ref systimer_armv8_m_dev_t
 *
 * \note This function doesn't check if dev is NULL.
 */
void systimer_armv8_m_uninit(struct systimer_armv8_m_dev_t * dev);

/**
 * \brief Reads 64-bit physical counter value
 *
 * \param[in] dev Timer device struct \ref systimer_armv8_m_dev_t
 *
 * \return 64-bit counter value
 *
 * \note This function doesn't check if dev is NULL.
 */
uint64_t systimer_armv8_m_get_counter_value(struct systimer_armv8_m_dev_t * dev);

/**
 * \brief Sets 64-bit compare value
 *        As soon as the physical up-counter reaches this value, the interrupt
 *        condition will be asserted \ref systimer_armv8_m_is_interrupt_asserted
 *
 * \param[in] dev Timer device struct \ref systimer_armv8_m_dev_t
 * \param[in] value 64-bit compare value
 *
 * \note This function doesn't check if dev is NULL.
 */
void systimer_armv8_m_set_compare_value(struct systimer_armv8_m_dev_t * dev, uint64_t value);

/**
 * \brief Reads 64-bit compare value
 *
 * \param[in] dev Timer device struct \ref systimer_armv8_m_dev_t
 *
 * \return 64-bit compare value
 *
 * \note This function doesn't check if dev is NULL.
 */
uint64_t systimer_armv8_m_get_compare_value(struct systimer_armv8_m_dev_t * dev);

/**
 * \brief Sets frequency register in Hz
 *        Hardware does not interpret the value of the register, so it's only
 *        for software can discover the frequency of the system counter.
 *
 * \param[in] dev Timer device struct \ref systimer_armv8_m_dev_t
 * \param[in] value frequency in Hz
 *
 * \note This function doesn't check if dev is NULL.
 */
void systimer_armv8_m_set_counter_freq(struct systimer_armv8_m_dev_t * dev, uint32_t value);

/**
 * \brief Reads frequency register in Hz
 *
 * \param[in] dev Timer device struct \ref systimer_armv8_m_dev_t
 *
 * \return frequency in Hz
 *
 * \note This function doesn't check if dev is NULL.
 */
uint32_t systimer_armv8_m_get_counter_freq(struct systimer_armv8_m_dev_t * dev);

/**
 * \brief Sets 32-bit down-counter timer value
 *        'Down-counter timer set' automatically sets the compare register by
 *        compare register = current counter + this timer value
 *
 *        As soon as the timer value reaches zero, the interrupt condition will
 *        be asserted \ref systimer_armv8_m_is_interrupt_asserted.
 *        Reaching zero doesn't stop the timer.
 *
 * \param[in] dev Timer device struct \ref systimer_armv8_m_dev_t
 * \param[in] value 32-bit timer value
 *
 * \note This function doesn't check if dev is NULL.
 */
void systimer_armv8_m_set_timer_value(struct systimer_armv8_m_dev_t * dev, uint32_t value);

/**
 * \brief Reads down-counter timer value
 *
 * \param[in] dev Timer device struct \ref systimer_armv8_m_dev_t
 *
 * \return 32-bit timer value
 *
 * \note This function doesn't check if dev is NULL.
 */
uint32_t systimer_armv8_m_get_timer_value(struct systimer_armv8_m_dev_t * dev);

/**
 * \brief Enables timer
 *        Enables timer output signal and interrupt status assertion
 *        \ref systimer_armv8_m_is_interrupt_asserted
 *
 * \param[in] dev Timer device struct \ref systimer_armv8_m_dev_t
 *
 * \note This function doesn't check if dev is NULL.
 */
void systimer_armv8_m_enable_timer(struct systimer_armv8_m_dev_t * dev);

/**
 * \brief Disables timer
 *        Disables timer output signal. Interrupt status will be unknown
 *        \ref systimer_armv8_m_is_interrupt_asserted
 *
 * \param[in] dev Timer device struct \ref systimer_armv8_m_dev_t
 *
 * \note This function doesn't check if dev is NULL.
 */
void systimer_armv8_m_disable_timer(struct systimer_armv8_m_dev_t * dev);

/**
 * \brief Polls timer enable status
 *
 * \param[in] dev Timer device struct \ref systimer_armv8_m_dev_t
 *
 * \return true if enabled, false otherwise
 *
 * \note This function doesn't check if dev is NULL.
 */
bool systimer_armv8_m_is_timer_enabled(struct systimer_armv8_m_dev_t * dev);

/**
 * \brief Enables timer interrupt
 *
 * \param[in] dev Timer device struct \ref systimer_armv8_m_dev_t
 *
 * \note This function doesn't check if dev is NULL.
 */
void systimer_armv8_m_enable_interrupt(struct systimer_armv8_m_dev_t * dev);

/**
 * \brief Disables timer interrupt
 *
 * \param[in] dev Timer device struct \ref systimer_armv8_m_dev_t
 *
 * \note This function doesn't check if dev is NULL.
 */
void systimer_armv8_m_disable_interrupt(struct systimer_armv8_m_dev_t * dev);

/**
 * \brief Polls timer interrupt enable status
 *
 * \param[in] dev Timer device struct \ref systimer_armv8_m_dev_t
 *
 * \return true if enabled, false otherwise
 *
 * \note This function doesn't check if dev is NULL.
 */
bool systimer_armv8_m_is_interrupt_enabled(struct systimer_armv8_m_dev_t * dev);

/**
 * \brief Polls timer interrupt status
 *        It's asserted if
 *          1. Auto-Inc is disabled and counter reaches compare value
 *          OR
 *          2. Auto-Inc is enabled and counter reaches auto-inc value
 *
 * \param[in] dev Timer device struct \ref systimer_armv8_m_dev_t
 *
 * \return true if asserted, false otherwise
 *
 * \note This function doesn't check if dev is NULL.
 */
bool systimer_armv8_m_is_interrupt_asserted(struct systimer_armv8_m_dev_t * dev);

/**
 * \brief Reads auto-increment value
 *        This value is automatically calculated by
 *        auto-inc = current counter + auto-inc reload
 *
 * \param[in] dev Timer device struct \ref systimer_armv8_m_dev_t
 *
 * \return 64-bit auto-increment value
 *
 * \note This function doesn't check if dev is NULL.
 */
uint64_t systimer_armv8_m_get_autoinc_value(struct systimer_armv8_m_dev_t * dev);

/**
 * \brief Sets 32-bit auto-increment reload value
 *        Auto-Inc value is automatically calculated by adding this reload value
 *        to the current counter.
 *        If the counter reaches auto-inc value, interrupt status is asserted
 *        and auto-inc value is automatically set by current reload.
 *
 * \param[in] dev Timer device struct \ref systimer_armv8_m_dev_t
 * \param[in] value 32-bit reload value
 *
 * \note This function doesn't check if dev is NULL.
 */
void systimer_armv8_m_set_autoinc_reload(struct systimer_armv8_m_dev_t * dev, uint32_t value);

/**
 * \brief Reads auto-increment reload value
 *
 * \param[in] dev Timer device struct \ref systimer_armv8_m_dev_t
 *
 * \return 32-bit auto-increment reload value
 *
 * \note This function doesn't check if dev is NULL.
 */
uint32_t systimer_armv8_m_get_autoinc_reload(struct systimer_armv8_m_dev_t * dev);

/**
 * \brief Enables auto-increment mode
 *
 * \param[in] dev Timer device struct \ref systimer_armv8_m_dev_t
 *
 * \note This function doesn't check if dev is NULL.
 */
void systimer_armv8_m_enable_autoinc(struct systimer_armv8_m_dev_t * dev);

/**
 * \brief Disables auto-increment mode
 *
 * \param[in] dev Timer device struct \ref systimer_armv8_m_dev_t
 *
 * \note This function doesn't check if dev is NULL.
 */
void systimer_armv8_m_disable_autoinc(struct systimer_armv8_m_dev_t * dev);

/**
 * \brief Polls auto-increment enable status
 *
 * \param[in] dev Timer device struct \ref systimer_armv8_m_dev_t
 *
 * \return true if enabled, false otherwise
 *
 * \note This function doesn't check if dev is NULL.
 */
bool systimer_armv8_m_is_autoinc_enabled(struct systimer_armv8_m_dev_t * dev);

/**
 * \brief Clears auto-increment mode interrupt flag
 *
 * \param[in] dev Timer device struct \ref systimer_armv8_m_dev_t
 *
 * \note This function doesn't check if dev is NULL.
 */
void systimer_armv8_m_clear_autoinc_interrupt(struct systimer_armv8_m_dev_t * dev);

/**
 * \brief Polls auto-increment implementation status
 *
 * \param[in] dev Timer device struct \ref systimer_armv8_m_dev_t
 *
 * \return true if implemented, false otherwise
 *
 * \note This function doesn't check if dev is NULL.
 */
bool systimer_armv8_m_is_autoinc_implemented(struct systimer_armv8_m_dev_t * dev);

#ifdef __cplusplus
}
#endif
#endif /* __SYSTIMER_ARMV8_M_DRV_H__ */
